#include "stdafx.h"
#include "MLP.h"
#include "layer.h"
#include "neuron.h"
#include "dendrite.h"
#include "Dendrite Form.h"

namespace nnse
{
	extern io_module io_m;

	// Constructor and Destructor

	template <class T, class U> dendrite<T, U>::dendrite(neuron<T, U>^ p, unsigned int d_n)
	: parent(p), number(d_n)
	{
		io_m.startup_stream << "Constructing dendrite " << get_number() << std::endl;
		weight=((static_cast<U>(rand())/RAND_MAX)*2.0)-1.0;
		input=0.0;
		output=0.0;
		previous_weight_change=0.0;
		dendrite_form=nullptr;
	}

	template <class T, class U> dendrite<T, U>::~dendrite()
	{
		io_m.startup_stream << "Destructing dendrite " << get_number() << std::endl;
	}

	// Accessor Functions

	template <class T, class U> inline MLP<T, U>^ dendrite<T, U>::get_parent_MLP()
	{
		return get_parent_neuron()->get_parent_layer()->get_parent_MLP();
	}

	template <class T, class U> inline neuron<T, U>^ dendrite<T, U>::get_parent_neuron()
	{
		return parent;
	}

	template <class T, class U> inline unsigned int dendrite<T, U>::get_number()
	{
		return number;
	}

	template <class T, class U> inline void dendrite<T, U>::put_number(unsigned int n)
	{
		number=n;
	}
	template <class T, class U> inline U dendrite<T, U>::get_weight()
	{
		return weight;
	}

	template <class T, class U> inline void dendrite<T, U>::put_weight(U w)
	{
		weight=w;
		io_m.results_stream << "NEW DENDRITE WEIGHT: " << get_weight() << std::endl;
		put_output();
	}

	template <class T, class U> inline U dendrite<T, U>::get_input()
	{
		return input;
	}

	template <class T, class U> inline void dendrite<T, U>::put_input(U i)
	{
		input=i;
		io_m.results_stream << "NEW DENDRITE INPUT: " << get_input() << std::endl;
		put_output();
	}

	template <class T, class U> inline U dendrite<T, U>::get_output()
	{
		return output;
	}

	template <class T, class U> inline void dendrite<T, U>::put_output()
	{
		output=calc_output();
		io_m.results_stream << "NEW DENDRITE OUTPUT: " << get_output() << std::endl;
		if (dendrite_form)
		{
			dendrite_form->update_form();
		}
	}

	template <class T, class U> inline U dendrite<T, U>::get_previous_weight_change()
	{
		return previous_weight_change;
	}

	template <class T, class U> inline void dendrite<T, U>::put_previous_weight_change(U p_w_c)
	{
		previous_weight_change=p_w_c;
		io_m.results_stream << "NEW DENDRITE PREVIOUS WEIGHT CHANGE: " << get_previous_weight_change() << std::endl;
		if (dendrite_form)
		{
			dendrite_form->update_form();
		}
	}

	// Worker Functions

	template <class T, class U> void dendrite<T, U>::update_weight(U OUT, U DELTA)
	{
		put_weight(get_weight()+calc_weight_change(OUT, DELTA));
	}

	template <class T, class U> U dendrite<T, U>::calc_output()
	{
		return get_weight()*get_input();
	}

	template <class T, class U> U dendrite<T, U>::calc_weight_change(U OUT, U DELTA)
	{
		U weight_change=0.0;
		if (get_parent_MLP()->get_momentum_flag())
		{
			weight_change=(get_parent_MLP()->get_learning_rate()*OUT*DELTA)+(get_parent_MLP()->get_momentum_value()*get_previous_weight_change());
    		put_previous_weight_change(weight_change);
		}
		else
		{
			weight_change=get_parent_MLP()->get_learning_rate()*OUT*DELTA;
		}
		return weight_change;
	}

	// GUI Functions

	template <class T, class U> void dendrite<T, U>::display_form()
	{
		if (!dendrite_form)
		{
			dendrite_form=gcnew SimpleGUI::Dendrite<T, U>(this);
		}
		dendrite_form->Show();
		dendrite_form->update_form();
	}

	template <class T, class U> void dendrite<T, U>::invalidate_form()
	{
		dendrite_form=nullptr;
	}

	// Serialisation Functions

	template <class T, class U> void dendrite<T, U>::read(std::wifstream& in)
	{
		unsigned int number;
		in >> number;
		put_number(number);
		U weight;
		in >> weight;
		put_weight(weight);
		U input;
		in >> input;
		put_input(input);
		U output;
		in >> output;
		this->output=output;
		U previous_weight_change;
		in >> previous_weight_change;
		put_previous_weight_change(previous_weight_change);
	}

	template <class T, class U> void dendrite<T, U>::write(std::wofstream& out)
	{
		out << get_number() << " ";
		out << get_weight() << " ";
		out << get_input() << " ";
		out << get_output() << " ";
		out << get_previous_weight_change() << " ";
	}

	template <class T, class U> void dendrite<T, U>::display(std::wofstream& out)
	{
		out << "Dendrite:" << std::endl;
		out << "Number: " << get_number() << std::endl;
		out << "Weight: " << get_weight() << std::endl;
		out << "Input: " << get_input() << std::endl;
		out << "Output: " << get_output() << std::endl;
		out << "Previous Weight Change: " << get_previous_weight_change() << std::endl;
	}

	template <class T, class U> std::wifstream& operator>>(std::wifstream& in, dendrite<T, U>% d)
	{
		if (in.good())
		{
			d.read(in);
			return in;
		}
		else
		{
			throw std::ios_base::failure("\nError in wifstream& >> dendrite<T, U>%");
		}
	}

	template <class T, class U> std::wofstream& operator<<(std::wofstream& out, dendrite<T, U>% d)
	{
		if (out.good())
		{
			d.write(out);
			return out;
		}
		else
		{
			throw std::ios_base::failure("\nError in wofstream& << dendrite<T, U>%");
		}
	}

	// Explicit Instantiation

	template ref class dendrite<bool, double>;
	template ref class dendrite<unsigned char, double>;
	template std::wifstream& operator>>(std::wifstream& in, dendrite<bool, double>% d);
	template std::wifstream& operator>>(std::wifstream& in, dendrite<unsigned char, double>% d);
	template std::wofstream& operator<<(std::wofstream& out, dendrite<bool, double>% d);
	template std::wofstream& operator<<(std::wofstream& out, dendrite<unsigned char, double>% d);
}
